package cloud.daodao.storm.security.service;

import cloud.daodao.storm.common.security.model.secret.*;
import cloud.daodao.storm.security.config.AppConfig;
import cloud.daodao.storm.security.constant.CacheConstant;
import cloud.daodao.storm.security.model.Aes;
import cloud.daodao.storm.security.model.Rsa;
import cloud.daodao.storm.security.repository.AesRepository;
import cloud.daodao.storm.security.repository.RsaRepository;
import cloud.daodao.storm.common.security.error.SecurityError;
import cloud.daodao.storm.common.error.AppException;
import cloud.daodao.storm.common.util.security.RsaUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.time.Duration;
import java.util.Optional;

/**
 * @author DaoDao
 **/
@Slf4j
@Service
@Transactional
public class SecretService {

    @Resource
    private AesRepository aesRepository;

    @Resource
    private RsaRepository rsaRepository;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private AppConfig appConfig;

    public AesData aesSelect(AesSelect param) {
        String id = param.getId();
        Aes aes = aes(id);
        AesData aesData = new AesData();
        BeanUtils.copyProperties(aes, aesData);
        return aesData;
    }

    private Aes aes(String id) {
        Aes aes;

        Boolean cacheEnable = appConfig.getCacheEnable();
        if (cacheEnable) {
            String key = CacheConstant.SECRET_AES + id;
            String json = stringRedisTemplate.opsForValue().get(key);
            if (null == json) {
                Optional<Aes> optional = aesRepository.findById(id);
                if (optional.isEmpty()) {
                    throw new AppException(SecurityError.AES_NOT_EXIST, id);
                }
                aes = optional.get();
                Duration duration = appConfig.getCacheDuration();
                stringRedisTemplate.opsForValue().set(key, aes.toJson(), duration);
            } else {
                aes = new Aes().ofJson(json);
            }
        } else {
            Optional<Aes> optional = aesRepository.findById(id);
            if (optional.isEmpty()) {
                throw new AppException(SecurityError.AES_NOT_EXIST, id);
            }
            aes = optional.get();
        }

        return aes;
    }

    public AesData aesUpsert(AesUpsert param) {
        String id = param.getId();
        Optional<Aes> optional = aesRepository.findById(id);
        Aes aes = optional.orElseGet(Aes::new);
        BeanUtils.copyProperties(param, aes);

        aes = aesRepository.save(aes);

        String key = CacheConstant.SECRET_AES + id;
        stringRedisTemplate.delete(key);

        AesData aesData = new AesData();
        BeanUtils.copyProperties(aes, aesData);

        return aesData;
    }

    public RsaData rsaSelect(RsaSelect param) {
        String id = param.getId();
        Rsa rsa = rsa(id);
        RsaData rsaData = new RsaData();
        BeanUtils.copyProperties(rsa, rsaData);
        return rsaData;
    }

    private Rsa rsa(String id) {
        Rsa rsa;

        Boolean cacheEnable = appConfig.getCacheEnable();
        if (cacheEnable) {
            String key = CacheConstant.SECRET_RSA + id;
            String json = stringRedisTemplate.opsForValue().get(key);
            if (null == json) {
                Optional<Rsa> optional = rsaRepository.findById(id);
                if (optional.isEmpty()) {
                    throw new AppException(SecurityError.RSA_NOT_EXIST, id);
                }
                rsa = optional.get();
                Duration duration = appConfig.getCacheDuration();
                stringRedisTemplate.opsForValue().set(key, rsa.toJson(), duration);
            } else {
                rsa = new Rsa().ofJson(json);
            }
        } else {
            Optional<Rsa> optional = rsaRepository.findById(id);
            if (optional.isEmpty()) {
                throw new AppException(SecurityError.RSA_NOT_EXIST, id);
            }
            rsa = optional.get();
        }

        return rsa;
    }

    public RsaData rsaUpsert(RsaUpsert param) {
        String id = param.getId();
        Optional<Rsa> optional = rsaRepository.findById(id);
        Rsa rsa = optional.orElseGet(Rsa::new);
        BeanUtils.copyProperties(param, rsa);

        String serverPublicKey = rsa.getServerPublicKey();
        String serverPrivateKey = rsa.getServerPrivateKey();
        if (null == serverPublicKey || null == serverPrivateKey || serverPublicKey.isEmpty() || serverPrivateKey.isEmpty()) {
            try {
                KeyPair keyPair = RsaUtil.keyPair(2048);
                PrivateKey privateKey = keyPair.getPrivate();
                PublicKey publicKey = keyPair.getPublic();
                serverPublicKey = RsaUtil.publicKey(publicKey);
                serverPrivateKey = RsaUtil.privateKey(privateKey);
            } catch (NoSuchAlgorithmException | IOException e) {
                log.error(e.getMessage(), e);
                throw new AppException(SecurityError.RSA_GENERATE_EXCEPTION);
            }
            rsa.setServerPublicKey(serverPublicKey);
            rsa.setServerPrivateKey(serverPrivateKey);
        }
        rsa = rsaRepository.save(rsa);

        String key = CacheConstant.SECRET_RSA + id;
        stringRedisTemplate.delete(key);

        RsaData rsaData = new RsaData();
        BeanUtils.copyProperties(rsa, rsaData);

        return rsaData;
    }

}
